<?php

namespace App\Common\Util;

use App\Common\Interfaces\EasySearchMacroInterface;
use App\Common\Model\Model;
use App\Common\Util\EasySearchMacro\Trash;
use think\Collection;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\db\Query;

class EasySearch
{
    private array $where = [];
    /**
     * @var callable
     */
    private $searchHandle = null;
    /**
     * @var callable|string
     */
    private mixed $searchHandleField = '';

    /**
     * 是否查询总数
     * @var false
     */
    private bool $totalRequired = true;

    /**
     * 扩展操作
     *
     * @var array|callable[]|EasySearchMacroInterface[]
     */
    private array $macro = [];

    /**
     * @param Query|Model $queryBuilder
     *
     */
    private function __construct(private readonly Query|Model $queryBuilder)
    {
        $searchArr = TP::request()->get('search');
        $searchArr = array_filter((array)$searchArr, 'is_array');

        $this->resolveWhere($searchArr['searchType'] ?? [], $searchArr['search'] ?? [], $searchArr['searchField'] ?? []);
    }

    /**
     * @param Query|Model $queryBuilder
     *
     * @return EasySearch
     * @throws \Exception
     */
    public static function create(Query|Model $queryBuilder): EasySearch
    {
        $easySearch = new self($queryBuilder);
        $easySearch->addMacro(new Trash());

        return $easySearch;
    }

    /**
     * @param bool $fetchSql
     *
     * @return array{"data":Collection|\think\model\Collection,"total":int}
     * @throws DbException
     * @throws DataNotFoundException
     * @throws ModelNotFoundException
     */
    public function getData(bool $fetchSql = false): array
    {
        $this->getBuilder();

        $total = $this->getTotal();

        $this->pagination();

        $this->order();

        if ($fetchSql) {
            $this->queryBuilder->fetchSql();
        }

        $data = $this->queryBuilder->select();

        return compact('data', 'total');
    }

    /**
     * @return Query|Model
     * @date 2023/5/21
     */
    public function getBuilder(): Query|Model
    {
        $this->executeSearch();

        foreach ($this->macro as $macro) {
            call_user_func($macro['callable'], $this->queryBuilder, ...$macro['params']);
        }

        return $this->queryBuilder;
    }

    /**
     * 取消总数查询
     *
     * @return $this
     * @author chenlong<vip_chenlong@163.com>
     * @date   2022/6/5
     */
    public function totalCancellations(): static
    {
        $this->totalRequired = false;

        return $this;
    }


    /**
     * 去除自动搜索，升级为自定义处理搜索
     *
     * @param callable|string $field 处理的字段，可直接传回调，直接传回调 全部条件都会轮询回调此回调
     * @param callable|null $searchHandle 字处理的回调，参数：query, field, value,
     * 处理过的请返回 true，否则false,
     * 返回false,则会继续执行自动查询
     * @return $this
     */
    public function upgradeSearch(callable|string $field, callable $searchHandle = null): static
    {
        if ($searchHandle === null && is_callable($field)){
            $this->searchHandle = $field;
            return $this;
        }

        $this->searchHandle      = $searchHandle;
        $this->searchHandleField = $field;

        return $this;
    }

    /**
     * 解析条件
     *
     * @param array $searchType
     * @param array $searchValue
     * @param array $searchFields
     *
     * @return void
     */
    private function resolveWhere(array $searchType = [], array $searchValue = [], array $searchFields = []): void
    {
        foreach ($searchValue as $field => $value) {
            if ($value === null || $value === [] || $value === '') {
                continue;
            }

            $this->where[] = [$searchFields[$field] ?? $field, $searchType[$field] ?? '=', $value];
        }
    }

    /**
     * 执行搜索
     *
     * @author chenlong<vip_chenlong@163.com>
     * @date   2022/6/4
     */
    private function executeSearch(): void
    {
        foreach ($this->where as [$field, $type, $value]) {
            if ($this->searchHandle) {
                $isHandle = false;
                if ($this->searchHandleField && $this->searchHandleField === $field){
                    $isHandle = call_user_func($this->searchHandle, $this->queryBuilder, $field, $value);
                } elseif (!$this->searchHandleField) {
                    $isHandle = call_user_func($this->searchHandle, $this->queryBuilder, $field, $value);
                }

                if ($isHandle) continue;
            }

            match (strtoupper($type)) {
                'IN'         => $this->queryBuilder->whereIn($field, $value),
                'BETWEEN'    => $this->queryBuilder->whereBetween($field, $value),
                'LIKE'       => $this->queryBuilder->where($field, $type, "%$value%"),
                'LIKE_RIGHT' => $this->queryBuilder->where($field, "like", "$value%"),
                default      => $this->queryBuilder->where($field, $type, $value)
            };
        }
    }

    /**
     * 分页自动判断
     *
     * @author chenlong<vip_chenlong@163.com>
     * @date   2022/6/5
     */
    private function pagination(): void
    {
        if ($page = TP::request()->get('page')) {
            $this->queryBuilder->page($page, TP::request()->get('pageSize', 10));
        }
    }

    /**
     * @author chenlong<vip_chenlong@163.com>
     * @date   2022/6/5
     */
    private function order(): void
    {
        if (!$order = TP::request()->get('order')) {
            return;
        }

        is_array($order) or $order = json_decode($order, true);
        $field = $order['field'] ?? '';
        $order = $order['order'] ?? '';
        if (!$order || !$field) {
            return;
        }

        $order = match (strtoupper($order)){
            'DESCENDING', 'DES', => 'DESC',
            default              => 'ASC',
        };

        $originOrder = $this->queryBuilder->getOptions('order');
        $originOrder = [
            $field => $order,
            ...$originOrder
        ];

        $this->queryBuilder->setOption('order', $originOrder);
    }

    /**
     * 获取总数
     *
     * @return int
     * @throws DbException
     * @author chenlong<vip_chenlong@163.com>
     * @date   2022/6/4
     */
    private function getTotal(): int
    {
        if (!$this->totalRequired) return 0;

        return TP::request()->get('page') ? $this->queryBuilder->count() : 0;
    }

    /**
     * 设置扩展
     *
     * @param callable|EasySearchMacroInterface|string $macro
     * @param mixed ...$params
     * @return EasySearch
     * @throws \Exception
     */
    public function addMacro(callable|EasySearchMacroInterface|string $macro, ...$params): EasySearch
    {
        if (is_string($macro) && !is_callable($macro)) {
            $macro = new $macro();

            if (!$macro instanceof EasySearchMacroInterface){
                throw new \Exception("无效的macro");
            }
        }

        $this->macro[] = [
            'callable' => $macro,
            'params'   => $params
        ];

        return $this;
    }

    /**
     * 获取Query参数
     *
     * @param string|null $key
     * @param             $default
     *
     * @return array|mixed|null
     */
    public static function query(string $key = null, $default = null): mixed
    {
        $query = TP::request()->get('query');
        $res = [];
        if (!$query) {
            return $default;
        }
        foreach (explode('&', urldecode($query)) as $item) {
            list($k, $v) = explode('=', $item);
            $res[$k] = $v;
        }

        return $key ? ($res[$key] ?? $default) : $res;
    }
}
